using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using OpenTap.Plugins.Interfaces.Common;
using OpenTap.Plugins.Interfaces.PSU;

namespace OpenTap.Plugins.Psu
{
    [Display("Keysight N6700 PSU Driver", Group: "OpenTap.Plugins", Description: "Keysight N6700 Modular Power Supply Driver")]
    public class PsuN6700 : PsuBase
    {
        [Flags]
        public enum ELeavePowerOn
        {
            Module1 = 1,
            Module2 = 2,
            Module3 = 4,
            Module4 = 8
        }

        public PsuN6700()
        {
            ModulePowerInUse = true;
            Module1InUse = true;    // Defaults
            Module2InUse = true;
            Module3InUse = true;
            Module4InUse = true;

            MeterView = EView.Meter4; //FIX: to prevent null values.
            //will initialize N6700 module list according to the max amount of modules available in EModule enum.
            N6700Modules = new List<N6700Module>(Enum.GetValues(typeof(EModule)).Cast<int>().Max());

            LeavePowerOn = 0;
            Name = "N6700";
        }

        #region Settings

        [Display("Please set how may meters will be in view.")]
        public EView MeterView { get; set; }

        [Display("Module1 InUse. True/False")]
        public bool Module1InUse { get; set; }

        [Display("Module2 InUse. True/False")]
        public bool Module2InUse { get; set; }

        [Display("Module3 InUse. True/False")]
        public bool Module3InUse { get; set; }

        [Display("Module4 InUse. True/False")]
        public bool Module4InUse { get; set; }

        [Display(Name: "Leave power ON", Group: "Close Parameters", Order: 3,
            Description: "Leave power ON")]
        public ELeavePowerOn LeavePowerOn { get; set; }

        #endregion

        /// <summary>
        /// Open procedure for the instrument.
        /// </summary>
        public override void Open()
        {
            lock (_instrumentLock)
            {
                if (IsConnected) throw new InvalidOperationException("Psu is already connected!");
                base.Open();

                if(LeavePowerOn == 0)
                    Reset();

                //configure PSU view.
                SetMeterView(MeterView);
                var modules = Enum.GetValues(typeof(EModule));
                Array.Reverse(modules); // To reverse the loop in order to select first available module
                var i = modules.Length;
                foreach (EModule module in modules)
                {
                    MaxVoltageV = double.NaN; //just to make sure.
                    MaxCurrentA = double.NaN;
                    var moduleInUse = true;

                    if (module == EModule.Module1 && Module1InUse ||
                        module == EModule.Module2 && Module2InUse ||
                        module == EModule.Module3 && Module3InUse ||
                        module == EModule.Module4 && Module4InUse)
                    {
                        try
                        {
                            MaxVoltageV = ScpiQuery<double>(FormatCommandToTargetModule("SENS:VOLT:RANG? MAX,", module));
                            Log.Info("Module:" + i + " MAX voltage is: " + MaxVoltageV);

                            MaxCurrentA = ScpiQuery<double>(FormatCommandToTargetModule("SENS:CURR:RANG? MAX,", module));
                            Log.Info("Module:" + i + " MAX current is: " + MaxCurrentA);
                        }
                        catch
                        {
                            Log.Warning("Reading module:" + i + " information error, module would be disabled.");
                            ScpiCommand("*CLS;*WAI;\n");
                            moduleInUse = false;
                        }
                    }else   moduleInUse = false;

                    Log.Info("Module" + module + " InUse status is: " + moduleInUse);


                    N6700Modules.Add(new N6700Module(module, moduleInUse, MaxCurrentA, MaxVoltageV));
                    i--;
                    if (moduleInUse)
                        SelectedN6700Module = module; // as default, first available module is selected
                }

                Log.Info(SelectedN6700Module.ToString() + " is now set active!");
            }
        }

        /// <inheritdoc />
        public override void Close()
        {
            lock (_instrumentLock)
            {
                Log.Info("PsuN6700: Close");
                foreach (var module in N6700Modules)
                {
                    if (!module.ModuleInUse) continue;
                    if (!(
                        (module.ModuleId == EModule.Module1 && (LeavePowerOn & ELeavePowerOn.Module1) != 0) ||
                        (module.ModuleId == EModule.Module2 && (LeavePowerOn & ELeavePowerOn.Module2) != 0) ||
                        (module.ModuleId == EModule.Module3 && (LeavePowerOn & ELeavePowerOn.Module3) != 0) ||
                        (module.ModuleId == EModule.Module4 && (LeavePowerOn & ELeavePowerOn.Module4) != 0))
                       )

                        SetOutputState(EState.Off, module.ModuleId);
                }

                if (LeavePowerOn > 0)
                    base.Close(true);
                else
                    base.Close();
            }
        }

        /// <inheritdoc />
        public override string IdnModule(EModule moduleNumber, out int numberOfModules)
        {
            if (!Enum.IsDefined(typeof(EModule), moduleNumber))
                throw new InvalidEnumArgumentException(nameof(moduleNumber), (int)moduleNumber, typeof(EModule));

            numberOfModules = ScpiQuery<int>("SYST:CHAN:COUN?");
            if(numberOfModules < 1)
                return "Modules not found.";

            var targetModule = N6700Modules.Find(x => x.ModuleId == moduleNumber);
            if (!targetModule.ModuleInUse)
                return moduleNumber + " not in use.";

            var model = ScpiQuery(FormatCommandToTargetModule("SYST:CHAN:MOD? ", moduleNumber));
            var options = ScpiQuery(FormatCommandToTargetModule("SYST:CHAN:OPT? ", moduleNumber));
            var serialNumber = ScpiQuery(FormatCommandToTargetModule("SYST:CHAN:SER? ", moduleNumber));

            return moduleNumber + "," + model + "," + options + "," + serialNumber;
        }

        /// <inheritdoc />
        protected override void ScpiCmd_SetVolt(double voltageVolts, EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                ScpiCommand(
                    FormatCommandToTargetModule(
                        "VOLT " + voltageVolts.ToString("#.000", CultureInfo.InvariantCulture) + " ,",
                        module));
            }
        }

        /// <inheritdoc />
        public override double GetCurrentLimit(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                return ScpiQuery<double>(FormatCommandToTargetModule("VOLT?", module));
            }
        }

        /// <inheritdoc />
        protected override void ScpiCmd_SetCurr(double currentAmps, EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                ScpiCommand(
                    FormatCommandToTargetModule(
                        "CURR " + currentAmps.ToString("#.000", CultureInfo.InvariantCulture) + " ,",
                        module));
            }
        }

        /// <inheritdoc />
        public override double GetVoltageLevel(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                return ScpiQuery<double>(FormatCommandToTargetModule("CURR?", module));
            }
        }

        /// <inheritdoc />
        public override double GetVoltageLevelMax(EModule module = EModule.Module1)
        {
            throw new Exception("Command is not supported YET! Add this to driver if need to use.");
        }

        public override void SetOverVoltageLevel(double voltage, EModule module = EModule.Module1)
        {
            throw new NotImplementedException();
        }

        public override double GetOverVoltageLevel(EModule module = EModule.Module1)
        {
            throw new NotImplementedException();
        }

        public override double GetOverVoltageLevelMax(EModule module = EModule.Module1)
        {
            throw new NotImplementedException();
        }

        public override void SetPolarity(EPolarity polarity, EModule module = EModule.Module1)
        {
            var statenow = ScpiQuery(FormatCommandToTargetModule("OUTP:REL:POL?", module));
            if (statenow.Contains("NORM") && polarity == EPolarity.Normal) return;
            if (statenow.Contains("REV") && polarity == EPolarity.Reverse) return;

            var voltageOn = ScpiQuery(FormatCommandToTargetModule("OUTP:STAT? ", module));
            if (!voltageOn.Contains("0"))
            {
                ScpiCommand(FormatCommandToTargetModule("OUTP:STAT OFF, ", module));
                Thread.Sleep(MinDelayIfPolarityChange);
            }

            ScpiCommand(polarity == EPolarity.Reverse
                ? FormatCommandToTargetModule("OUTP:REL:POL REV, ", module)
                : FormatCommandToTargetModule("OUTP:REL:POL NORM, ", module));
        }


        /// <inheritdoc />
        public override void SetCurrentProtectionState(EState state, EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                if (!Enum.IsDefined(typeof(EState), state))
                    throw new InvalidEnumArgumentException("state", (int) state, typeof(EState));
                //Command is  different from N5700 series.
                ScpiCommand(IsModularPowerInUse(EState.On == state ? "CURR:PROT:STAT ON," : "CURR:PROT:STAT OFF,"), module);
            }
        }

        /// <inheritdoc />
        public override EState GetCurrentProtectionStatus(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                //same function, but command differs from N5700 series.
                var status = ScpiQuery<short>(IsModularPowerInUse("CURR:PROT:STAT?", module)) & 2;
                return status == 0 ? EState.Off : EState.On;
            }
        }

        /// <inheritdoc />
        public override EState GetOutputState(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                var response = ScpiQuery(IsModularPowerInUse("OUTP:STAT?", module));
                if (response.Contains("0"))
                    return (EState.Off);

                if (response.Contains("1"))
                    return (EState.On);

                throw new Exception("GetOutputState did not catch the wanted(0/1) answer from: " + response);
            }
        }

        /// <inheritdoc />
        public override void SetOutputState(EState state, EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                if (!Enum.IsDefined(typeof(EState), state))
                    throw new InvalidEnumArgumentException("state", (int) state, typeof(EState));
                // command has , and target that differs from N5700
                ScpiCommand(IsModularPowerInUse(EState.On == state ? "OUTP:STAT ON," : "OUTP:STAT OFF,", module));
            }
        }


        /// <inheritdoc />
        public override void SetMeterView(EView view)
        {
            lock (_instrumentLock)
            {
                var baseCommand = "DISP:WIND:VIEW ";
                string scpiCommand; //Was " = null;", but ReSharper recommended to remove initialization.
                switch (view)
                {
                    case EView.Meter1:
                        scpiCommand = baseCommand + "METER1";
                        break;
                    case EView.Meter4:
                        scpiCommand = baseCommand + "METER4";
                        break;
                    default: //Normally should not ever come to this, but developers are developers
                        throw new Exception("Internal error! Unhandled case in PsuN6700.SetMeterView");
                }

                Log.Info("Setting PSU View:" + scpiCommand);
                ScpiCommand(scpiCommand);
            }
        }
    }
}